package com.gyj.server.core;

import com.gyj.server.entity.DeltaRegistry;
import com.gyj.server.entity.RecentlyChangedServiceInstance;
import com.gyj.server.enums.OperationTypeEnum;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;


/**
 * 服务注册表
 * @author Gao
 * @date 2021/6/26
 */
public class ServiceRegistry {

    private static final Logger logger = LogManager.getLogger(ServiceRegistry.class);

    /**
     * 线程休眠时间间隔
     */
    public static final long RECENTLY_CHANGED_ITEM_CHECK_INTERVAL = 3000L;

    /**
     * 最近3分钟变化的队列
     */
    public static final long RECENTLY_CHANGED_ITEM_EXPIRED = 3 * 60 * 1000L;

    /**
     * 最近变更的服务实例的队列
     * ConcurrentLinkedQueue线程安全的队列，先进先出，通过CAS保证线程安全
     * size则是遍历节点数量，可能不是很准确，先是获取头节点，再遍历，
     * 但是遍历的过程中可能有其他线程插入了新的节点
     */
    private ConcurrentLinkedQueue<RecentlyChangedServiceInstance> recentlyChangedQueue
            = new ConcurrentLinkedQueue<>();

    /**
     * 服务注册表的读写锁
     */
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private ReadLock readLock = lock.readLock();
    private WriteLock writeLock = lock.writeLock();

    /**
     * 服务注册表是单例
     */
    private static ServiceRegistry instance = new ServiceRegistry();

    private ServiceRegistry() {
        RecentlyChangedQueueMonitor monitor = new RecentlyChangedQueueMonitor();
        monitor.setDaemon(true);
        monitor.start();
    }

    /**
     * 获取服务注册表单例
     * @return
     */
    public static ServiceRegistry getInstance() {
        return instance;
    }

    /**
     * 加读锁
     */
    public void readLock() {
        this.readLock.lock();
    }

    /**
     * 释放读锁
     */
    public void readUnlock() {
        this.readLock.unlock();
    }

    /**
     * 加写锁
     */
    public void writeLock() {
        this.writeLock.lock();
    }

    /**
     * 释放写锁
     */
    public void writeUnlock() {
        this.writeLock.unlock();
    }


    /**
     * 核心的内存数据结构：注册表
     * Map：key是服务名称，value是这个服务的所有的服务实例
     * Map<String, ServiceInstance>：key是服务实例id，value是服务实例的信息
     * ConcurrentHashMap，可以保证并发读可以通过volatile读到最新的数据，不用加锁
     * 果是频繁的更新map中的key,value对，多线程并发写的安全性
     *
     */
    private Map<String, Map<String, ServiceInstance>> registry = new ConcurrentHashMap<>();

    /**
     * 监控最近3分钟变得队列的线程
     */
    private class RecentlyChangedQueueMonitor extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    // 多线程加锁保证线程安全
                    try {
                        writeLock.lock();
                        RecentlyChangedServiceInstance instance = null;
                        while ((instance = recentlyChangedQueue.peek()) != null) {
                            // 如果实例在队列中保存超过3分钟，则从队列中删除，队列是按照顺序依次添加到队尾中，先进先出的
                            // 队列头部一定是加入时间最早的，最近加入的在队尾
                            if (System.currentTimeMillis() - instance.getRecentlyChangedTime() > RECENTLY_CHANGED_ITEM_EXPIRED) {
                                recentlyChangedQueue.poll();
                                logger.info("监控最近3分钟变得队列的线程，队头出队后的队列recentlyChangedQueue:{}", recentlyChangedQueue);
                            }
                        }
                    } finally {
                        writeLock.unlock();
                    }
                    Thread.sleep(RECENTLY_CHANGED_ITEM_CHECK_INTERVAL);
                } catch (Exception e) {
                    logger.error("监控最近3分钟变得队列的线程e:{}", e);
                }
            }
        }
    }

    /**
     * 服务注册，服务端接收到客户端服务注册到注册表数据结构中
     * 之前也是用synchronized修饰
     * @param instance
     */
    public void register(ServiceInstance instance) {
        try {
            logger.info("[服务注册], 加写锁，instance:{}", instance);
            writeLock.lock();
            Map<String, ServiceInstance> serviceRegistryMap = registry.get(instance.getServiceName());
            if (Objects.isNull(serviceRegistryMap)) {
                serviceRegistryMap = new ConcurrentHashMap<>(16);
                registry.put(instance.getServiceName(), serviceRegistryMap);
            }
            serviceRegistryMap.put(instance.getServiceInstanceId(), instance);
            logger.info("[服务注册]，注册服务是instance:{}, 服务注册表registry:{}", instance, registry);

            long currentTime = System.currentTimeMillis();
            RecentlyChangedServiceInstance recentlyChangedInstance =
                    new RecentlyChangedServiceInstance(instance, currentTime, OperationTypeEnum.REGISTER.getCode());
            recentlyChangedQueue.offer(recentlyChangedInstance);
            logger.info("[服务注册]，注册完成后添加到最近变化的队列后recentlyChangedQueue:{}", recentlyChangedQueue);
        } finally {
            logger.info("[服务注册]释放写锁");
            writeLock.unlock();
        }
    }

    /**
     * 获取服务注册实例信息
     * @param serviceName 服务名称
     * @param serviceInstanceId 服务实例id
     * 之前也是用synchronized修饰
     * @return
     */
    public ServiceInstance getServiceInstance(String serviceName, String serviceInstanceId) {
        try {
            readLock.lock();
            logger.info("[获取服务注册实例信息], 加读锁，服务名称:{}, 服务实例id", serviceName, serviceInstanceId);
            Map<String, ServiceInstance> serviceInstanceMap = registry.get(serviceName);
            if (Objects.isNull(serviceInstanceMap)) {
                return null;
            }
            return serviceInstanceMap.get(serviceInstanceId);
        } finally {
            logger.info("[获取服务注册实例信息], 释放读锁");
            readLock.unlock();
        }
    }

    /**
     * 获取整个服务注册表
     * 之前也是用synchronized修饰
     * 这里的读不需要加锁, 用了concurrentHashMap可以保证
     * 在读的时候读到最新的数据，用的volatile修饰了
     * @return
     */
    public Map<String, Map<String, ServiceInstance>> getRegistry() {
        return registry;
    }

    /**
     * 获取最近有变化的注册表
     * 之前也是用synchronized修饰
     * @return
     */
    public DeltaRegistry getDeltaRegistry() {
        Long totalCount = 0L;
        // 注册表服务实例的集合
        Collection<Map<String, ServiceInstance>> values = registry.values();
        for (Map<String, ServiceInstance> entry : values) {
            totalCount += entry.size();
        }
        DeltaRegistry deltaRegistry = new DeltaRegistry(recentlyChangedQueue, totalCount);
        logger.info("获取最近有变化的注册表, 封装返回数据deltaRegistry:{}", deltaRegistry);
        return deltaRegistry;
    }

    /**
     * 删除服务实例
     * 之前也是用synchronized修饰
     * @param serviceName
     * @param serviceInstanceId
     */
    public void remove(String serviceName, String serviceInstanceId) {
        try {
            logger.info("[删除服务实例]加读锁，删除服务名:{}, 服务实例id:{}", serviceName, serviceInstanceId);
            writeLock.lock();
            Map<String, ServiceInstance> serviceInstanceMap = registry.get(serviceName);
            if (Objects.isNull(serviceInstanceMap)) {
                return;
            }
            logger.info("[删除服务实例],根据服务名称获取服务实例列表map:{}", serviceInstanceMap);
            // 获取具体的服务实例信息，添加到队列中
            ServiceInstance serviceInstance = serviceInstanceMap.get(serviceInstanceId);
            // 删除服务之前把它，也就是最近变化的数据添加到队列中
            long currentTime = System.currentTimeMillis();
            RecentlyChangedServiceInstance instance =
                    new RecentlyChangedServiceInstance(serviceInstance, currentTime, OperationTypeEnum.REMOVE.getCode());
            recentlyChangedQueue.offer(instance);
            logger.info("[删除服务实例],添加最近变化的队列recentlyChangedQueue:{}", recentlyChangedQueue);

            serviceInstanceMap.remove(serviceInstanceId);
            logger.info("[删除服务实例],serviceName:{}, serviceInstanceId:{}", serviceName, serviceInstanceId);
        } finally {
            logger.info("[删除服务实例]释放写锁");
            writeLock.unlock();
        }
    }
}
